// 使用select实现并发服务器
// server code
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#define PORT 8888
#define BACKLOG 20
#define MAX_FD 10

void dealClientRequest(int clientFd, char* filename);

int main(void)
{
    int listenFd;
    listenFd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serverAddr;
    bzero(&serverAddr, sizeof serverAddr);
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8888);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    int err = bind(listenFd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    if (err < 0)
    {
        printf("bind failed.\n");
        exit(-1);
    }
    err = listen(listenFd, BACKLOG);
    if (err < 0)
    {
        printf("listen failed.\n");
        exit(-1);
    }

    int fd_all[MAX_FD];
    
    memset(&fd_all, -1, sizeof fd_all); // 把所有值设置为-1
    fd_all[0] = listenFd;

    fd_set fd_read; // 真正传入select的参数（会被修改）
    FD_ZERO(&fd_read);  // 清空
    FD_SET(listenFd, &fd_read);  // 添加监听套结字

    int maxFd = listenFd;   // 当前最大套结字描述符

    fd_set fd_read_bak = fd_read;

    while (1)
    {
        fd_read = fd_read_bak;  // 重新设置fd_read
        int err = select(maxFd+1, &fd_read, NULL, NULL, NULL);  // 设置为阻塞
        if (err < 0)
        {
            printf("failed to select.\n");
            exit(0);
        }
        else if (err == 0)  // 阻塞情况不会返回0
        {
            printf("timeout.\n");
        }
        else
        {
            printf("%d socket response.\n", err);
        }
        // 处理监听套接字描
        if (FD_ISSET(listenFd, &fd_read))
        {
            struct sockaddr_in clientAddr;
            socklen_t clientLen;
            int clientFd = accept(listenFd, (struct sockaddr*)&clientAddr, &clientLen);
            if (clientFd < 0)
            {
                printf("accept failed.\n");
                exit(0);
            }
            
            // 输出新接受的套接字
            char * clientIp = inet_ntoa(clientAddr.sin_addr);
            printf("new connection --- ip:%s, port:%d\n", clientIp, ntohs(clientAddr.sin_port));

            // 加入新套接字到描述符数组
            for (int i = 0; i < MAX_FD; ++i)
            {
                if (fd_all[i] != -1)
                {
                    continue;
                }
                fd_all[i] = clientFd;
                break;
            }

            // 加入新套接字到fd_read
            FD_SET(clientFd, &fd_read_bak);
            if (clientFd > maxFd)
            {
                maxFd = clientFd;
            }
        }
        // 处理客户端套接字
        for (int i = 1; i < MAX_FD; ++i)
        {
            // 依次判断每个套接字描述符是否就绪
            if (!FD_ISSET(fd_all[i], &fd_read))
            {
                continue;
            }
            printf("fd:%d is ready.\n", fd_all[i]);
            char buffer[4096] = {0};
            int err = read(fd_all[i], buffer, sizeof buffer);
            if (err > 0)
            {
                printf("fd:%d get %d bytes.\n", fd_all[i], err);
                // 处理客户端发来信息代码
                char* filename = (char*)malloc(sizeof(char) * err);
                strcpy(filename, buffer);
                dealClientRequest(fd_all[i], filename);
            }
            else 
            {
                if (err == 0)
                {
                    printf("fd:%d socket close.\n", fd_all[i]);
                }
                else if (err < 0)
                {
                    printf("fd:%d socket break.\n", fd_all[i]);
                }
                FD_CLR(fd_all[i], &fd_read_bak);
                close(fd_all[i]);
                fd_all[i] = -1;
            }
        }
    }
    return 0;
}

void dealClientRequest(int clientFd, char* filename)
{
    printf("start deal file.\n");
    char buffer[4096] = {0};
    int fileFd = open(filename, O_RDONLY);

    if (fileFd < 0)
    {
        printf("%s open failed.\n", filename);
        strcpy(buffer, "file open failed.");
    }
    else
    {
        struct stat statbuf; // 获得文件的大小，得到文件字节数
        stat(filename, &statbuf);
        int size = statbuf.st_size;
        sprintf(buffer, "file size: %d", size);
    }
    
    write(clientFd, buffer, strlen(buffer)+1);

    if (fileFd > 0)
    {
        int size = 0;
        while ((size = read(fileFd, buffer, sizeof buffer - 1)) > 0)
        {
            buffer[size] = '\0';
            printf("%s", buffer);
            printf("size = %d\n", size);
            send(clientFd, buffer, size, 0);
        }
        printf("transfer over.\n");
        close(fileFd);
    }

    free(filename);
}